/**
 * Copyright (C) 2006-2012 phloc systems
 * http://www.phloc.com
 * office[at]phloc[dot]com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.phloc.genericode;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.xml.bind.JAXBException;
import javax.xml.bind.MarshalException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.UnmarshalException;
import javax.xml.bind.Unmarshaller;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.dom.DOMSource;

import org.genericode._2006.ns.codelist._0.CodeListDocument;
import org.genericode._2006.ns.codelist._0.CodeListSetDocument;
import org.genericode._2006.ns.codelist._0.ColumnSetDocument;
import org.genericode._2006.ns.codelist._0.ObjectFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Node;

import com.phloc.commons.io.IReadableResource;
import com.phloc.commons.io.resource.ClassPathResource;
import com.phloc.commons.state.ESuccess;
import com.phloc.commons.xml.XMLFactory;
import com.phloc.commons.xml.transform.ResourceStreamSource;

/**
 * This is the reader and writer for Genericode 0.4 documents.
 * 
 * @author philip
 */
public class Genericode04Marshaller extends AbstractGenericodeMarshaller
{
  private static final Logger s_aLogger = LoggerFactory.getLogger (Genericode04Marshaller.class);
  private static final Genericode04Marshaller s_aInstance = new Genericode04Marshaller ();

  // Schema resources
  private static final IReadableResource [] GENERICODE_XSDS = { new ClassPathResource ("/schemas/genericode/genericode-code-list-0.4.xsd"),
                                                               new ClassPathResource ("/schemas/genericode/xml.xsd") };

  protected Genericode04Marshaller ()
  {}

  /**
   * @return The default instance of this marshaller.
   */
  @Nonnull
  public static final Genericode04Marshaller getInstance ()
  {
    return s_aInstance;
  }

  /**
   * Read a code list from the specified source
   * 
   * @param aSource
   *        The source to be read from. May not be <code>null</code>.
   * @return <code>null</code> in case reading fails
   */
  @Nullable
  public CodeListDocument readCodeList (@Nonnull final Source aSource)
  {
    if (aSource == null)
      throw new NullPointerException ("source");

    try
    {
      final Unmarshaller aUnmarshaller = createUnmarshaller (CodeListDocument.class, GENERICODE_XSDS);
      return aUnmarshaller.unmarshal (aSource, CodeListDocument.class).getValue ();
    }
    catch (final UnmarshalException ex)
    {
      // The JAXB specification does not mandate how the JAXB provider
      // must behave when attempting to unmarshal invalid XML data. In
      // those cases, the JAXB provider is allowed to terminate the
      // call to unmarshal with an UnmarshalException.
      s_aLogger.error ("Unmarshal exception reading Genericode 0.4 codelist", ex);
    }
    catch (final JAXBException ex)
    {
      s_aLogger.warn ("JAXB Exception reading Genericode 0.4 codelist", ex);
    }
    return null;
  }

  /**
   * Read a code list using the standard marshaller.
   * 
   * @param aResource
   *        The resource to read from. May not be <code>null</code>.
   * @return <code>null</code> in case reading fails.
   */
  @Nullable
  public CodeListDocument readCodeList (@Nonnull final IReadableResource aResource)
  {
    if (aResource == null)
      throw new NullPointerException ("resource");

    return readCodeList (new ResourceStreamSource (aResource));
  }

  /**
   * Read a code list using the standard marshaller.
   * 
   * @param aNode
   *        The DOM node to read from. May not be <code>null</code>.
   * @return <code>null</code> in case reading fails.
   */
  @Nullable
  public CodeListDocument readCodeList (@Nonnull final Node aNode)
  {
    if (aNode == null)
      throw new NullPointerException ("node");

    return readCodeList (new DOMSource (aNode));
  }

  /**
   * Read a code list set from the specified source
   * 
   * @param aSource
   *        The source to be read from. May not be <code>null</code>.
   * @return <code>null</code> in case reading fails
   */
  @Nullable
  public CodeListSetDocument readCodeListSet (@Nonnull final Source aSource)
  {
    if (aSource == null)
      throw new NullPointerException ("source");

    try
    {
      final Unmarshaller aUnmarshaller = createUnmarshaller (CodeListSetDocument.class, GENERICODE_XSDS);
      return aUnmarshaller.unmarshal (aSource, CodeListSetDocument.class).getValue ();
    }
    catch (final UnmarshalException ex)
    {
      // The JAXB specification does not mandate how the JAXB provider
      // must behave when attempting to unmarshal invalid XML data. In
      // those cases, the JAXB provider is allowed to terminate the
      // call to unmarshal with an UnmarshalException.
      s_aLogger.error ("Unmarshal exception reading Genericode 0.4 codelistset", ex);
    }
    catch (final JAXBException ex)
    {
      s_aLogger.warn ("JAXB Exception reading Genericode 0.4 codelistset", ex);
    }
    return null;
  }

  /**
   * Read a code list set using the standard marshaller.
   * 
   * @param aResource
   *        The resource to read from. May not be <code>null</code>.
   * @return <code>null</code> in case reading fails.
   */
  @Nullable
  public CodeListSetDocument readCodeListSet (@Nonnull final IReadableResource aResource)
  {
    if (aResource == null)
      throw new NullPointerException ("resource");

    return readCodeListSet (new ResourceStreamSource (aResource));
  }

  /**
   * Read a code list set using the standard marshaller.
   * 
   * @param aNode
   *        The node to read from. May not be <code>null</code>.
   * @return <code>null</code> in case reading fails.
   */
  @Nullable
  public CodeListSetDocument readCodeListSet (@Nonnull final Node aNode)
  {
    if (aNode == null)
      throw new NullPointerException ("node");

    return readCodeListSet (new DOMSource (aNode));
  }

  /**
   * Read a column set from the specified source
   * 
   * @param aSource
   *        The source to be read from. May not be <code>null</code>.
   * @return <code>null</code> in case reading fails
   */
  @Nullable
  public ColumnSetDocument readColumnSet (@Nonnull final Source aSource)
  {
    if (aSource == null)
      throw new NullPointerException ("source");

    try
    {
      final Unmarshaller aUnmarshaller = createUnmarshaller (ColumnSetDocument.class, GENERICODE_XSDS);
      return aUnmarshaller.unmarshal (aSource, ColumnSetDocument.class).getValue ();
    }
    catch (final UnmarshalException ex)
    {
      // The JAXB specification does not mandate how the JAXB provider
      // must behave when attempting to unmarshal invalid XML data. In
      // those cases, the JAXB provider is allowed to terminate the
      // call to unmarshal with an UnmarshalException.
      s_aLogger.error ("Unmarshal exception reading Genericode 0.4 columnset", ex);
    }
    catch (final JAXBException ex)
    {
      s_aLogger.warn ("JAXB Exception reading Genericode 0.4 columnset", ex);
    }
    return null;
  }

  /**
   * Read a column set using the standard marshaller.
   * 
   * @param aResource
   *        The resource to read from. May not be <code>null</code>.
   * @return <code>null</code> in case reading fails.
   */
  @Nullable
  public ColumnSetDocument readColumnSet (@Nonnull final IReadableResource aResource)
  {
    if (aResource == null)
      throw new NullPointerException ("resource");

    return readColumnSet (new ResourceStreamSource (aResource));
  }

  /**
   * Read a column set using the standard marshaller.
   * 
   * @param aNode
   *        The node to read from. May not be <code>null</code>.
   * @return <code>null</code> in case reading fails.
   */
  @Nullable
  public ColumnSetDocument readColumnSet (@Nonnull final Node aNode)
  {
    if (aNode == null)
      throw new NullPointerException ("node");

    return readColumnSet (new DOMSource (aNode));
  }

  /**
   * Write the code list to the desired result XML object.
   * 
   * @param aCodeList
   *        The object to be serialized to XML. May not be <code>null</code>.
   * @param aResult
   *        The result the XML should be written to. May not be
   *        <code>null</code>.
   * @return {@link ESuccess}
   */
  @Nonnull
  public ESuccess writeCodeList (@Nonnull final CodeListDocument aCodeList, @Nonnull final Result aResult)
  {
    if (aCodeList == null)
      throw new NullPointerException ("codeList");
    if (aResult == null)
      throw new NullPointerException ("result");

    try
    {
      final Marshaller aMarshaller = createMarshaller (CodeListDocument.class, GENERICODE_XSDS);
      aMarshaller.marshal (new ObjectFactory ().createCodeList (aCodeList), aResult);
      return ESuccess.SUCCESS;
    }
    catch (final MarshalException ex)
    {
      s_aLogger.error ("Marshal exception writing Genericode 0.4 codelist", ex);
    }
    catch (final JAXBException ex)
    {
      s_aLogger.warn ("JAXB Exception writing Genericode 0.4 codelist", ex);
    }
    return ESuccess.FAILURE;
  }

  /**
   * Convert the code list to a DOM document using the standard marshaller.
   * 
   * @param aCodeList
   *        The object to be serialized to XML. May not be <code>null</code>.
   * @return <code>null</code> in case conversion fails.
   */
  @Nullable
  public Document writeCodeList (@Nonnull final CodeListDocument aCodeList)
  {
    if (aCodeList == null)
      throw new NullPointerException ("codeList");

    final Document aDoc = XMLFactory.newDocument ();
    return writeCodeList (aCodeList, new DOMResult (aDoc)).isSuccess () ? aDoc : null;
  }

  /**
   * Write the code list set to the desired result XML object.
   * 
   * @param aCodeListSet
   *        The object to be serialized to XML. May not be <code>null</code>.
   * @param aResult
   *        The result the XML should be written to. May not be
   *        <code>null</code>.
   * @return {@link ESuccess}
   */
  @Nonnull
  public ESuccess writeCodeListSet (@Nonnull final CodeListSetDocument aCodeListSet, @Nonnull final Result aResult)
  {
    if (aCodeListSet == null)
      throw new NullPointerException ("codelistset");
    if (aResult == null)
      throw new NullPointerException ("result");

    try
    {
      final Marshaller aMarshaller = createMarshaller (CodeListSetDocument.class, GENERICODE_XSDS);
      aMarshaller.marshal (new ObjectFactory ().createCodeListSet (aCodeListSet), aResult);
      return ESuccess.SUCCESS;
    }
    catch (final MarshalException ex)
    {
      s_aLogger.error ("Marshal exception writing Genericode 0.4 codelistset", ex);
    }
    catch (final JAXBException ex)
    {
      s_aLogger.warn ("JAXB Exception writing Genericode 0.4 codelistset", ex);
    }
    return ESuccess.FAILURE;
  }

  /**
   * Convert the code list set to a DOM document using the standard marshaller.
   * 
   * @param aCodeListSet
   *        The object to be serialized to XML. May not be <code>null</code>.
   * @return <code>null</code> in case conversion fails.
   */
  @Nullable
  public Document writeCodeListSet (@Nonnull final CodeListSetDocument aCodeListSet)
  {
    if (aCodeListSet == null)
      throw new NullPointerException ("codelistset");

    final Document aDoc = XMLFactory.newDocument ();
    return writeCodeListSet (aCodeListSet, new DOMResult ()).isSuccess () ? aDoc : null;
  }

  /**
   * Write the column set to the desired result XML object.
   * 
   * @param aColumnSet
   *        The object to be serialized to XML. May not be <code>null</code>.
   * @param aResult
   *        The result the XML should be written to. May not be
   *        <code>null</code>.
   * @return {@link ESuccess}
   */
  @Nullable
  public ESuccess writeColumnSet (@Nonnull final ColumnSetDocument aColumnSet, @Nonnull final Result aResult)
  {
    if (aColumnSet == null)
      throw new NullPointerException ("columnset");
    if (aResult == null)
      throw new NullPointerException ("result");

    try
    {
      final Marshaller aMarshaller = createMarshaller (ColumnSetDocument.class, GENERICODE_XSDS);
      aMarshaller.marshal (new ObjectFactory ().createColumnSet (aColumnSet), aResult);
      return ESuccess.SUCCESS;
    }
    catch (final MarshalException ex)
    {
      s_aLogger.error ("Marshal exception writing Genericode 0.4 columnset", ex);
    }
    catch (final JAXBException ex)
    {
      s_aLogger.warn ("JAXB Exception writing Genericode 0.4 columnset", ex);
    }
    return ESuccess.FAILURE;
  }

  /**
   * Convert the column set to a DOM document using the standard marshaller.
   * 
   * @param aColumnSet
   *        The object to be serialized to XML. May not be <code>null</code>.
   * @return <code>null</code> in case conversion fails.
   */
  @Nullable
  public Document writeColumnSet (@Nonnull final ColumnSetDocument aColumnSet)
  {
    if (aColumnSet == null)
      throw new NullPointerException ("columnset");

    final Document aDoc = XMLFactory.newDocument ();
    return writeColumnSet (aColumnSet, new DOMResult ()).isSuccess () ? aDoc : null;
  }
}
